package com.crunch.core;

import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

public class Input {
	public static final int WINDOW_EVENT        = 0x00000001;
	public static final int KEY_EVENT           = 0x00000002;
	public static final int MOUSE_EVENT         = 0x00000004;
	public static final int MOUSE_WHEEL_EVENT   = 0x00000008;
	public static final int MOUSE_MOTION_EVENT  = 0x00000010;
	public static final int ALL_EVENTS          = 0xFFFFFFFF;

	public static abstract class EventListener {
		public int eventMask = 0;
		public abstract void processEvent(AWTEvent e, int eventType);
	}

	private Set<EventListener> eventListeners = new HashSet<EventListener>();

	private static class Event {
		public AWTEvent event;
		public int eventMask;
		Event(AWTEvent event, int eventMask) {
			this.event = event;
			this.eventMask = eventMask;
		}
	}

	private ArrayList<Event> events = new ArrayList<Event>();

	// structures that hold the last state (for keys and mouse, whether the button is down)
	HashMap<Integer, Boolean> keysDown = new HashMap<Integer, Boolean>(); // indexed by key
	HashMap<Integer, Boolean> mouseButtonsDown = new HashMap<Integer, Boolean>(); // indexed by mouse button
	int mouseX, mouseY;

	Input(Window window) {
		window.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				pushEvent(e, WINDOW_EVENT);
			}
		});

		window.canvas.addKeyListener(new KeyListener() {
			@Override
			public void keyTyped(KeyEvent e) {
				pushEvent(e, KEY_EVENT);
			}

			@Override
			public void keyPressed(KeyEvent e) {
				pushEvent(e, KEY_EVENT);
			}

			@Override
			public void keyReleased(KeyEvent e) {
				pushEvent(e, KEY_EVENT);
			}
		});

		window.canvas.addMouseListener(new MouseListener() {
			@Override
			public void mouseClicked(MouseEvent e) {
				pushEvent(e, MOUSE_EVENT);
			}

			@Override
			public void mousePressed(MouseEvent e) {
				pushEvent(e, MOUSE_EVENT);
			}

			@Override
			public void mouseReleased(MouseEvent e) {
				pushEvent(e, MOUSE_EVENT);
			}

			@Override
			public void mouseEntered(MouseEvent e) {}

			@Override
			public void mouseExited(MouseEvent e) {}
		});

		window.canvas.addMouseWheelListener(new MouseWheelListener() {
			@Override
			public void mouseWheelMoved(MouseWheelEvent e) {
				pushEvent(e, MOUSE_WHEEL_EVENT);
			}
		});

		window.canvas.addMouseMotionListener(new MouseMotionListener() {
			@Override
			public void mouseDragged(MouseEvent e) {
				pushEvent(e, MOUSE_MOTION_EVENT);
			}

			@Override
			public void mouseMoved(MouseEvent e) {
				pushEvent(e, MOUSE_MOTION_EVENT);
			}
		});

		Point p = MouseInfo.getPointerInfo().getLocation();
		Point c = window.canvas.getLocationOnScreen();
		mouseX = p.x - c.x;
		mouseY = p.y - c.y;
	}

	private synchronized void pushEvent(AWTEvent event, int eventMask) {
		events.add(new Event(event, eventMask));
	}

	public void addEventListener(EventListener l) {
		eventListeners.add(l);
	}

	public void removeEventListener(EventListener l) {
		eventListeners.remove(l);
	}

	synchronized void processEvents() {
		for (Event e : events) {
			if (e.eventMask == KEY_EVENT) {
				KeyEvent ke = (KeyEvent) e.event;
				if (ke.getID() == KeyEvent.KEY_PRESSED) {
					keysDown.put(ke.getKeyCode(), true);
				} else if (ke.getID() == KeyEvent.KEY_RELEASED) {
					keysDown.put(ke.getKeyCode(), false);
				}
			}
			if (e.eventMask == MOUSE_EVENT) {
				MouseEvent me = (MouseEvent) e.event;
				if (me.getID() == MouseEvent.MOUSE_PRESSED) {
					mouseButtonsDown.put(me.getButton(), true);
				} else if (me.getID() == MouseEvent.MOUSE_RELEASED) {
					mouseButtonsDown.put(me.getButton(), false);
				}
			}
			if (e.eventMask == MOUSE_MOTION_EVENT) {
				MouseEvent me = (MouseEvent) e.event;
				mouseX = me.getX();
				mouseY = me.getY();
			}

			for (EventListener l : eventListeners) {
				if ((e.eventMask & l.eventMask) != 0) {
					l.processEvent(e.event, e.eventMask);
				}
			}
		}
		events.clear();
	}

	public boolean isKeyDown(int keyCode) {
		if (keysDown.containsKey(keyCode)) {
			return keysDown.get(keyCode);
		} else {
			// key has not yet been encountered, so it is not pressed
			return false;
		}
	}

	public boolean isMouseButtonDown(int buttonCode) {
		if (mouseButtonsDown.containsKey(buttonCode)) {
			return mouseButtonsDown.get(buttonCode);
		} else {
			// mouse button has not yet been encountered, so it is not pressed
			return false;
		}
	}

	public Point getMousePosition() {
		return new Point(mouseX, mouseY);
	}
}
